package cmu.andersonlab.hardpropagate;

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.event.*;

import java.net.*;
import java.io.*;

import java.util.*;

public class session implements Runnable, progress_listener, ActionListener, WindowListener {
    private Socket sock;
    private trial[] trials;
    private int trial_idx;
    private JFrame frame;
    private log lg = null;
    private String subject_id;

    public session(Socket client) {
        this.sock = client;

        Random rng = new Random();
        int id = rng.nextInt(10000);
        String prefix = client == null ? "i":"m";
        while (log.exists (prefix + id)) id = rng.nextInt();
        subject_id = prefix + id;
    }

    public trial[] read_trials (String filename) {
        ArrayList<trial> acc = new ArrayList<trial>();
        BufferedReader in = null;
        String line;
        try {
            in = new BufferedReader (new FileReader(session_list.get_session_file(filename)));
            while ((line = in.readLine()) != null) {
                line = line.trim();
                if (line.length() > 0) {
                    String[] parts = line.split(",");
                    String[] top = parts[0].trim().split(" +");
                    String[] bottom = parts[1].trim().split(" +");
                    int arrow_to = Integer.parseInt(parts[2].trim());
                    int blue_tile = Integer.parseInt(parts[3].trim());
                    String col_tmp = parts[4].trim();
                    String color;
                    if (col_tmp.equals("green")) {
                        color = "green";
                    } else if (col_tmp.equals("blue")) {
                        color = "blue";
                    } else {
                        color = "gray";
                    }
                    trial t = new trial (top[0], top[1], top[2],
                                         bottom[0], bottom[1], bottom[2],
                                         arrow_to, blue_tile, color, acc.size()+1);
                    t.progressor = this;
                    t.lg = lg;
                    acc.add(t);
                }
            }
        } catch (IOException e) {
            System.out.println(e);
        } finally {
            try {
                if (in != null) in.close();
            } catch (IOException e) {
                System.out.println(e);
            }
        }
        return acc.toArray(new trial[0]);
    }

    public void start_session(String name, boolean use_system_time, long time) {
        frame.getContentPane().removeAll();
        lg = new log(subject_id, use_system_time);
        lg.timestamp = time;
        lg.store("experiment", "start",
                 ":id", log.fmtstr(subject_id),
                 ":session", log.fmtstr(name));
        this.trials = read_trials(name);
        this.trial_idx = -1;
        next_problem();
    }

    public boolean validate_command(PrintStream out, String line[], boolean has_timestamp, int args) {
        if (line.length != args + 1 + (has_timestamp?1:0)) {
            out.println("parse-fail");
            return false;
        }
        if (has_timestamp) {
            try {
                lg.timestamp = Integer.parseInt(line[1]);
            } catch (NumberFormatException e) {
                out.println("parse-fail");
                return false;
            }
        }
        return true;
    }

    public boolean handle_line(String line, PrintStream out) {
        String args[] = line.split(" ");
        String cmd = args[0];

        if (cmd.equals ("quit")) {
            if (lg != null) {
                lg.store("experiment", "force-quit");
            }
            return true;
        } else if (cmd.equals("hide-window")) {
            frame.setVisible(false);
            out.println("success");
        } else if (cmd.equals("show-window")) {
            frame.setVisible(true);
            out.println("success");
        } else if (this.trials == null) {
            if (cmd.equals("start")) {
                if (args.length != 3) {
                    out.println("parse-fail");
                    return false;
                }
                if (session_list.exists(args[2])) {
                    int ts;
                    try {
                        ts = Integer.parseInt(args[1]);
                    } catch (NumberFormatException e) {
                        out.println("parse-fail");
                        return false;
                    }
                    start_session(args[2], false, ts);
                    out.println("start-problem 60000");
                } else {
                    out.println("fail");
                }
            } else if (cmd.equals("subject-id")) {
                if (args.length != 2) {
                    out.println("parse-fail");
                    return false;
                }
                subject_id = args[1];
                out.println("success");
            } else if (cmd.equals("list-sessions")) {
                String[] sessions = session_list.get_names();
                // Java doesn't have a join function
                for (int i=0; i<sessions.length; i++) {
                    out.print(sessions[i]);
                    if (i < sessions.length-1) out.print(",");
                }
                out.println();
            } else {
                out.println("no-session");
            }
        } else if (this.trial_idx >= this.trials.length) {
            out.println ("session-over");
        } else {
            trial t = this.trials[this.trial_idx];

            if (cmd.equals ("input")) {
                if (validate_command (out, args, true, 1)) {
                    if (t.add_input(args[2])) {
                        out.println("success");
                    } else {
                        out.println("fail");
                    }
                }
            } else if (cmd.equals ("open-keypad")) {
                if (validate_command (out, args, true, 0)) {
                    if (t.open_keypad()) {
                        out.println("success");
                    } else {
                        out.println("fail");
                    }
                }
            } else if (cmd.equals ("close-keypad")) {
                if (validate_command (out, args, true, 0)) {
                    if (t.close_keypad()) {
                        out.println("success");
                    } else {
                        out.println("fail");
                    }
                }
            } else if (cmd.equals ("timeout")) {
                if (validate_command (out, args, true, 0)) {
                    if (t.show_feedback(true)) {
                        out.println("start-feedback 2000");
                    } else {
                        out.println("fail");
                    }
                }
            } else if (cmd.equals ("done")) {
                if (validate_command (out, args, true, 0)) {
                    if (t.show_feedback(false)) {
                        out.println("start-feedback 2000");
                    } else {
                        out.println("fail");
                    }
                }
            } else if (cmd.equals ("next")) {
                if (validate_command (out, args, true, 0)) {
                    if (t.end()) {
                        if (trial_idx < trials.length) {
                            out.println("start-problem 60000");
                        } else {
                            out.println("session-over");
                        }
                    } else {
                        out.println("fail");
                    }
                }
            } else if (cmd.equals ("dump")) {
                out.println(t.dump());
            } else {
                out.println("unknown-command");
            }
        }
        return false;
    }

    public void display_session_picker() {
        String[] sessions = session_list.get_names();
        JPanel panel = new JPanel();
        BoxLayout box = new BoxLayout(panel, BoxLayout.Y_AXIS);
        JLabel label = new JLabel("Select a Session.");

        label.setAlignmentX(Component.CENTER_ALIGNMENT);

        panel.setPreferredSize(new Dimension(500,500));
        panel.setAlignmentY(Component.CENTER_ALIGNMENT);
        panel.setLayout(box);
        panel.add(Box.createVerticalGlue());
        panel.add(label);
        panel.add(Box.createRigidArea(new Dimension(0, 20)));

        for (int i=0; i<sessions.length; i++) {
            JButton button = new JButton(sessions[i]);
            button.setActionCommand(sessions[i]);
            button.setAlignmentX(Component.CENTER_ALIGNMENT);
            button.addActionListener(this);
            panel.add(button);
        }
        panel.add(Box.createVerticalGlue());

        frame.getContentPane().add(panel);
        frame.pack();
    }

    public void actionPerformed(ActionEvent e) {
        start_session(e.getActionCommand(), true, 0);
    }

    public void windowActivated (WindowEvent e) { }
    public void windowClosing (WindowEvent e) { }
    public void windowDeactivated (WindowEvent e) { }
    public void windowDeiconified (WindowEvent e) { }
    public void windowIconified (WindowEvent e) { }
    public void windowOpened (WindowEvent e) { }

    public void windowClosed (WindowEvent e) {
        if (lg != null) {
            lg.store("experiment", "force-quit");
            lg.close();
        }
    }


    public void display_message(String msg) {
        JLabel label = new JLabel(msg);
        label.setHorizontalAlignment(SwingConstants.CENTER);
        //label.setAlignmentX(Component.CENTER_ALIGNMENT);
        label.setPreferredSize(new Dimension(500,500));
        frame.getContentPane().add(label);
        frame.pack();
    }

    public void next_problem() {
        // FIXME: all gui updates have to be done in the gui
        // thread. But it seems dirty to include session logic in
        // there.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (trial_idx < 0) {
                        trial_idx = 0;
                    } else {
                        trial old = trials[trial_idx];
                        frame.getContentPane().remove(old);
                        trial_idx++;
                    }
                    if (trial_idx < trials.length) {
                        trial cur = trials[trial_idx];
                        frame.getContentPane().add(cur);
                        cur.setPreferredSize(new Dimension(500,500));
                        frame.pack();
                        cur.start(sock == null);
                    } else {
                        lg.store("experiment", "end", ":id", log.fmtstr(subject_id));
                        lg.close();
                        lg = null;
                        display_message("Session Finished.");
                    }
                }
            });
    }

    public void setup_window () {
        frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.addWindowListener(this);
        display_session_picker();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public void run () {
        System.out.println("Socket starting: " + this.sock);
        BufferedReader in = null;
        PrintStream out = null;
        String line;

        setup_window();
        try {
            in  = new BufferedReader(new InputStreamReader (sock.getInputStream()));
            out = new PrintStream (sock.getOutputStream(), true);
            while ((line = in.readLine()) != null) {
                line = line.trim();
                boolean quit = this.handle_line(line, out);
                if (quit) break;
            }
        } catch (IOException e) {
            System.err.println (e);
        } finally {
            if (lg != null) lg.close();
            try {
                if (in != null) in.close();
                if (out != null) out.close();
                this.sock.close();
            } catch (IOException e) {
                System.out.println(e);
            }
            frame.setVisible(false);
            frame.dispose();
            System.out.println("Socket ended: " + this.sock);
        }
    }

}
